using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

/// <summary>
/// 1.C# 端构建的 MVP 矩阵 和 Shader 中内置 MVP 的一致性
/// 2.Quad 顶点变换为 ScreenPosition 的完整拆解
/// </summary>
public class WorldPositionToScreenPosExample1 : MonoBehaviour
{
    /*
        Shader 中内置的一系列矩阵:
        
        UNITY_MATRIX_M     <=> Object To World Matrix <=> C# Unity 本地坐标系 Render.transform.localToWorldMatrix
        UNITY_MATRIX_I_M   <=> World To Object Matrix <=> C# Unity 本地坐标系 Render.transform.worldToLocalMatrix
        UNITY_MATRIX_V     <=> World To View Matrix   <=> worldToCameraMatrix
        UNITY_MATRIX_P     <=> View To HClip Matrix   <=> GL.GetGPUProjectionMatrix(camera.projectionMatrix, true);
        
        UNITY_MATRIX_MV    <=> Object To View Matrix  <=> worldToCameraMatrix * localToWorldMatrix
        UNITY_MATRIX_VP    <=> World To HClip Matrix  <=> GL.GetGPUProjectionMatrix(camera.projectionMatrix, true) * worldToCameraMatrix
        
        UNITY_MATRIX_MVP   <=> Object To HClip Matrix <=> matrixP * matrixV * matrixM
        
        UNITY_MATRIX_T_MV  <=>                      
        UNITY_MATRIX_IT_MV <=>   
    */

    [Header("测试 Render")] public Renderer DebugRender;

    [Header("测试 C#构建的 MATRIX_MVP 和 内置矩阵 结果一致")]
    public bool bUseMyMVPMatrix = false;

    private Material _DebugMaterial => DebugRender.material;
    private Mesh _DebugMesh => DebugRender.GetComponent<MeshFilter>().sharedMesh;

    private Camera _Camera;
    private Matrix4x4 _CurrentMatrixMVP = Matrix4x4.identity;
    private Vector4[] _VertexPositionOS;

    /// <summary>
    /// UNITY_MATRIX_M
    /// </summary>
    private Matrix4x4 _MatrixM => DebugRender.localToWorldMatrix;

    /// <summary>
    /// UNITY_MATRIX_V
    /// </summary>
    private Matrix4x4 _MatrixV => _Camera.worldToCameraMatrix;

    /// <summary>
    /// UNITY_MATRIX_P
    /// </summary>
    private Matrix4x4 _MatrixP => GL.GetGPUProjectionMatrix(_Camera.projectionMatrix, true);

    /// <summary>
    /// UNITY_MATRIX_MVP
    /// </summary>
    private Matrix4x4 _MatrixMVP => _MatrixP * _MatrixV * _MatrixM;

    private void Start()
    {
        _Camera = Camera.main;
    }

    private void Update()
    {
        if (_Camera == null)
            return;

        _CurrentMatrixMVP = _MatrixMVP;

        DebugRender.material.SetMatrix("DEBUG_MATRIX_MVP", _CurrentMatrixMVP);

        if (!bUseMyMVPMatrix)
        {
            _DebugMaterial.DisableKeyword("_USE_DEBUG_MVP_MATRIX");
        }
        else
        {
            _DebugMaterial.EnableKeyword("_USE_DEBUG_MVP_MATRIX");
        }

        if (_DebugMesh.vertices.Length > 10)
            return;

        // 获取 Vertex 本地坐标
        _VertexPositionOS = _DebugMesh.vertices.Select(p => new Vector4(p.x, p.y, p.z, 1)).ToArray();
    }

    private void OnGUI()
    {
        if (_Camera == null)
            return;

        if (_DebugMesh.vertices.Length > 10)
        {
            GUILayout.Label("测试 Mesh 顶点超过 10");
            GUILayout.Label("推荐使用 Quad 测试");
            return;
        }

        GUILayout.Label("推荐将 Game 视窗 分辨率调整至 960*640");
        GUILayout.Label("============================================");

        GUILayout.Label("顶点在 本地（物体）坐标系 下坐标 Vertex PositionOS :");
        GUILayout.Label(_VertexPositionOS.Select(p => p.ToString("F2")).ToArray().ArrayToString());
        GUILayout.Label($"本地坐标系（物体） 世界位置 Object Position : " +
                        $"{DebugRender.transform.position.ToString("F2")}");
        var positionWS = _VertexPositionOS.Select(p => _MatrixM * p).ToArray();
        GUILayout.Label("顶点在 世界坐标系 下坐标 Vertex PositionWS :");
        GUILayout.Label(positionWS.Select(p => p.ToString("F2")).ToArray().ArrayToString());
        var positionVS = positionWS.Select(p => _MatrixV * p).ToArray();
        GUILayout.Label("顶点在 视角坐标系 下坐标 Vertex PositionVS :");
        GUILayout.Label(positionVS.Select(p => p.ToString("F2")).ToArray().ArrayToString());
        GUILayout.Label("============================================");

        var positionCS = positionVS.Select(p => _MatrixP * p).ToArray();
        GUILayout.Label("顶点在 投影剪裁坐标系 下坐标 Vertex PositionCS :");
        GUILayout.Label(positionCS.Select(p => p.ToString("F2")).ToArray().ArrayToString());
        GUILayout.Label("移动 DebugRender 能感受到 PositionCS 和 屏幕空间坐标 具有相关性");
        GUILayout.Label("============================================");

        // NDC (Normalized Device Coordinate)
        // https://zhuanlan.zhihu.com/p/65969162
        GUILayout.Label("NDC空间(Normalized Device Coordinate) 是长宽高取值范围为 [-1, 1] 的立方体");
        GUILayout.Label("顶点在 NDC空间 下坐标 Vertex PositionNDC :");
        var positionNDC = positionCS.Select(p => new Vector3(p.x, -p.y, p.z) / p.w).ToArray();
        GUILayout.Label("vertexPositionNDC :");
        GUILayout.Label(positionNDC.Select(p => p.ToString("F2")).ToArray().ArrayToString());
        GUILayout.Label("此时能观察到点在屏幕空间左下为 (-1, -1)，右上为 (1, 1)");
        GUILayout.Label("============================================");

        var positionScreenPos = positionNDC.Select(p => new Vector2(
                (p.x / 2f + 0.5f) * Screen.width,
                (p.y / 2f + 0.5f) * Screen.height))
            .ToArray();
        GUILayout.Label("顶点的 Screen Position 坐标 Vertex ScreenPos :");
        GUILayout.Label(positionScreenPos.Select(p => p.ToString("F0")).ToArray().ArrayToString());
        GUILayout.Label("============================================");

        GUILayout.Label($"鼠标位置 Input.mousePosition : {Input.mousePosition.ToString("F0")}");
    }
}